package com.springboot.cloud.seckill.service.impl;

import com.springboot.cloud.common.core.entity.vo.Result;
import com.springboot.cloud.common.core.enums.OrderStatusEnum;
import com.springboot.cloud.common.core.enums.ResponseCodeEnum;
import com.springboot.cloud.common.core.util.UserContextHolder;
import com.springboot.cloud.common.web.entity.vo.SecKillMessage;
import com.springboot.cloud.seckill.dao.SeckillRepository;
import com.springboot.cloud.seckill.entity.SeckillActivity;
import com.springboot.cloud.seckill.service.ISeckillAdminService;
import com.springboot.cloud.seckill.service.ISeckillService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author godyao
 * @date 2022/3/26
 */
@Service
@Slf4j
public class ISeckillServiceImpl implements ISeckillService {
    /**
     * redis中url code前缀
     */
    private static final String SEC_KILL_URL_CODE_PREFIX = "SECKILLURLCODE:";

    /**
     * redis中商品订单key前缀
     */
    private static final String SEC_KILL_GOODS_KEY_PREFIX = "SECKILLORDER:";

    /**
     * redis中秒杀key前缀
     */
    private static final String SEC_KILL_KEY_PREFIX = "SECKILLGOODS:";
    /**
     * 内存标记 减少对redis的访问
     */
    private static final Map<Long, Boolean> GOODS_CAN_BY_MAP = new HashMap<>();

    @Autowired
    private SeckillRepository seckillRepository;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisScript unlockScript;

    @Autowired
    private RabbitMqSecKillSender rabbitMqSecKillSender;
    @Override
    public Result secKill(Long goodsId) {
        if (GOODS_CAN_BY_MAP.getOrDefault(goodsId, true)) {
            return new Result(ResponseCodeEnum.NO_STOCK, null);
        }
        final ValueOperations valueOperations = redisTemplate.opsForValue();
        final String username = UserContextHolder.getInstance().getUsername();
        final Long userId = Long.valueOf(UserContextHolder.getInstance().getContext().get("userId"));
        String userGoodKey = username + ":" + goodsId;
        if (valueOperations.getOperations().hasKey(SEC_KILL_GOODS_KEY_PREFIX + userGoodKey)) {
            return new Result(ResponseCodeEnum.SEC_KILL_LIMIT, null);
        }
        // 从缓存拿到商品信息
        final String key = SEC_KILL_KEY_PREFIX + goodsId;
        if (!valueOperations.getOperations().hasKey(key)) {
            // 从数据库load到缓存中 这里需要获取锁
            final String LOCK = SEC_KILL_KEY_PREFIX+"lock";
            // 当业务执行时间超锁所设置的有效期 防止锁被别人释放
            final String VALUE = UUID.randomUUID().toString();
            final Integer LOCK_EXPIRE_TIME = 5;
            if (valueOperations.setIfAbsent(LOCK, VALUE, LOCK_EXPIRE_TIME, TimeUnit.SECONDS)) {
                // 获取到锁 从数据库load数据到缓存
                final SeckillActivity secKillGoods = seckillRepository.findByGoodsId(goodsId).get();
                if (Objects.nonNull(secKillGoods)) {
                    valueOperations.set(key, secKillGoods.getStock());
                } else {
                    // 对于不合法的商品数据 防止缓存穿透（数据库和缓存都没有） 设置过期时间
                    valueOperations.set(key, 0, 5, TimeUnit.SECONDS);
                }
                // 释放锁 使用lua脚本来保证原子性
                if (((Boolean) redisTemplate.execute(unlockScript, Collections.singletonList(LOCK), VALUE))) {
                    log.info("释放锁成功");
                } else {
                    log.info("释放锁失败");
                }
            } else {
                log.info("获取锁失败");
                return new Result(ResponseCodeEnum.FAIL, null);
            }
        }
        if (valueOperations.decrement(key) >= 0) {
            // 下单操作放到rabbitmq中 削峰 解耦
            rabbitMqSecKillSender.sendForSecKillOrder(new SecKillMessage(username, goodsId, userId));
            return new Result(ResponseCodeEnum.WAITING, OrderStatusEnum.PENDING_PROCESS.getCode());

        } else {
            GOODS_CAN_BY_MAP.put(goodsId, true);
            valueOperations.increment(key);
            return new Result(ResponseCodeEnum.NO_STOCK, null);
        }
    }

    @Override
    public boolean checkUrlInvalid(String code, Long goodsId) {
        final String username = UserContextHolder.getInstance().getUsername();
        // secKillUrlCode:{username}:{goodsId}
        String key = SEC_KILL_URL_CODE_PREFIX + "" + username + ":" + goodsId;
        final String s = (String) redisTemplate.opsForValue().get(key);
        return code.equals(s);


    }
}
